#pragma once
#include "../../header.h"
#include <thread>
#include <mutex>


/**
 * @class C++11 实现一个线程安全的队列（阻塞队列）
 * @condtion_variable 如果为 true 就不阻塞, 如果是 false 就阻塞 (wait...uitl 直到满足什么条件才停止)
 * @T&& 使用万能引用来简化代码，又可以是左值又可以是右值
 * @mutex mutex 是不可重入锁，一定要注意，递归式调用会死锁
*/

template<class T, class Container = queue<T>>
class ConcurrentQueue {
private:
    using Self = ConcurrentQueue<T, Container>;
    constexpr static int MAX_SIZE = 20;             // 最多存放20个元素

    Container _q;
    int _max_sz;                                    // 最大个数
    // 锁, 实现队列的互斥，mutable 是为了防止 const 的情况，方便随时改变 mutex 的状态
    mutable mutex _mtx;                             
    // 条件变量, 如果为空就阻塞, 阻塞消费者
    condition_variable _empty_cv;
    // 条件变量，如果满了就阻塞，阻塞生产者
    condition_variable _full_cv;

public:
    ConcurrentQueue(int max_size = MAX_SIZE) 
        : _max_sz(max_size)
    {}

public:

    // 生产一个，如果没有空间，那么就阻塞
    template<class E>
    void push(E&& elem) {
        unique_lock<mutex> curlock(_mtx);
        _full_cv.wait(curlock, [this]() {
            return _q.size() < _max_sz;
        });

        // 保持右值特性
        _q.push(std::forward<E>(elem));
        _empty_cv.notify_one();
    }

    // 消费一个，如果队列为空，那么就阻塞
    void pop(T& out) {
        unique_lock<mutex> curlock(_mtx);
        _empty_cv.wait(curlock, [this]() {
            return !_q.empty();
        });
        out = move(_q.front());
        _q.pop();
        _full_cv.notify_one();
    }

    // 如果是想获取这篇资源（指针的形式）
    shared_ptr<T> pop() {
        unique_lock<mutex> curlock(_mtx);
        _empty_cv.wait(curlock, [this]() {
            return !_q.empty();
        });
        shared_ptr<T> res = make_shared<T>(_q.front());
        _q.pop();
        _full_cv.notify_one();
    }

    // 尝试生产，如果失败就 return
    template<class E>               // 这里一定要模板
    bool try_push(E&& elem) {
        unique_lock<mutex> curlock(_mtx);
        if (_q.size() == _max_sz) {
            return false;
        }
        _q.push(std::forward<E>(elem));
        _empty_cv.notify_one();
        return true;
    }

    bool try_pop(T& out) {
        unique_lock<mutex> curlock(_mtx);
        if (_q.empty()) {
            return false;
        }
        out = move(_q.front());
        _q.pop();
        _full_cv.notify_one();
        return true;
    }

    shared_ptr<T> try_pop() {
        unique_lock<mutex> curlock(_mtx);
        if (_q.empty()) {
            return make_shared<T>();
        }
        auto res = make_shared<T>(_q.front());
        _q.pop();
        _full_cv.notify_one();
        return res;
    }

private:
    bool is_empty() const {
        unique_lock<mutex> curlock(_mtx);
        return _q.empty();
    }

    bool is_full() const {
        unique_lock<mutex> curlock(_mtx);
        return _q.size() == _max_sz;
    }

    ConcurrentQueue(const Self&) = delete;

    Self& operator=(const Self&) = delete;
};